<?php

require_once 'HTTP/Request2.php';

require_once dirname(__FILE__) . '/Exception.php';
require_once dirname(__FILE__) . '/Response.php';
require_once dirname(__FILE__) . '/Config.php';
require_once dirname(__FILE__) . '/Store.php';

class GG_API_SimpleMethod {

	const FORMAT_JSON = "json";
	const FORMAT_XML = "xml";
	const FORMAT_RSS = "rss";
	const DEFAULT_FORMAT = self::FORMAT_JSON;

	const HTTP_METHOD_GET = "GET";
	const HTTP_METHOD_POST = "POST";
	const DEFAULT_HTTP_METHOD = self::HTTP_METHOD_GET;

	private static $paramNameEscapePreffix = "gg_";

	/**
	 * Array of param names that need to be escaped if used in API.
	 * E.g. "callback" will be escaped to "gg_callback".
	 */
	private static $paramNamesToEscape = array();

	/**
	 * @var GG_API_Config
	 */
	protected $config = null;

	/**
	 * @var GG_API_Store
	 */
	protected $store = null;

	/**
	 * @var string Method space.
	 */
	protected $space;

	/**
	 * @var string Method name.
	 */
	protected $name;

	/**
	 * @var string Response format: json, xml, rss.
	 */
	protected $format = null;

	/**
	 * @var string HTTP method: GET or POST.
	 */
	protected $httpMethod = self::DEFAULT_HTTP_METHOD;

	/**
	 * @var string JSONP callback that will be used if missed in method params.
	 */
	protected $jsonpCallback = null;

	/**
	 * @var string Method params. No OAuth params here.
	 */
	protected $params = array();

	protected $useRequestParams = null;

	public function __construct($configArray = null) {
		$config = new GG_API_Config();
		$this->setConfig($config);
		if (is_array($configArray) && count($configArray) > 0) {
			$config->setConfigArray($configArray);
		}
	}

	/**
	 * @return GG_API_Config
	 */
	public function getConfig() {
		return $this->config;
	}

	/**
	 * @param GG_API_Config $config Config that will be used in this method.
	 */
	public function setConfig($config) {
		if ($config instanceof GG_API_Config && $config !== $this->config) {
			$this->config = $config;
			$this->onConfigChanged();
		}
	}

	protected function onConfigChanged() {
		$config = $this->config;
		$store = GG_API_Store::newByConfig($config);
		$this->setStore($store);
	}

	protected function getConfigValue($key, $default = null) {
		return $this->getConfig()->get($key, $default);
	}

	/**
	 * @return GG_API_Store
	 */
	public function getStore() {
		return $this->store;
	}

	/**
	 * @param GG_API_Store $store
	 */
	public function setStore($store) {
		if ($store instanceof GG_API_Store) {
			$this->store = $store;
		}
	}

	public function getHttpMethod() {
		return $this->httpMethod;
	}

	/**
	 * @param string $httpMethod HTTP method: GET or POST.
	 */
	public function setHttpMethod($httpMethod) {
		$httpMethod = strtoupper($httpMethod);
		$this->httpMethod = $httpMethod;
	}

	public function isHttpMethodGet() {
		return $this->getHttpMethod() === self::HTTP_METHOD_GET;
	}

	public function isHttpMethodPost() {
		return $this->getHttpMethod() === self::HTTP_METHOD_POST;
	}

	public function getFormat() {
		$result = $this->format;
		if ($result === null) {
			$result = $this->getConfigValue('format', self::DEFAULT_FORMAT);
		}
		return $result;
	}

	/**
	 * @param string $format Response format: json, xml, rss.
	 */
	public function setFormat($format) {
		$format = strtolower($format);
		$this->format = $format;
	}

	public function isFormatJson() {
		return $this->getFormat() === self::FORMAT_JSON;
	}

	public function isFormatXml() {
		return $this->getFormat() === self::FORMAT_XML;
	}

	public function isFormatRss() {
		return $this->getFormat() === self::FORMAT_RSS;
	}

	public function getSpace() {
		return $this->space;
	}

	/**
	 * @param string $space Method space. E.g. 'ads', 'ads.images', 'regions',
	 * 'categories', 'profile', etc.
	 */
	public function setSpace($space) {
		$this->space = $space;
	}

	public function getName() {
		return $this->name;
	}

	/**
	 * @param string $space Method name. E.g. 'new', 'get', 'list', 'update',
	 * 'delete', etc.
	 */
	public function setName($name) {
		$this->name = $name;
	}

	public function getJsonpCallback() {
		$result = $this->jsonpCallback;
		if ($result === null) {
			$result = $this->getConfigValue('jsonpCallback');
		}
		return $result;
	}

	public function setJsonpCallback($jsonpCallback) {
		$this->jsonpCallback = $jsonpCallback;
	}

	public function setUseRequestParams($use) {
		if (!is_bool($use)) {
			$use = false;
		}
		$this->useRequestParams = $use;
	}

	public function isUseRequestParams() {
		$result = $this->useRequestParams;
		if ($result === null) {
			$result = $this->getConfigValue('useRequestParams', false);
		}
		return $result;
	}

	public function getParams() {
		$params = array();
		if ($this->isUseRequestParams()) {
			$params = $this->getRequestParams();
		} else {
			$params = $this->params;
		}
		return $params;
	}

	public function setParams($params) {
		if (!is_array($params)) {
			$params = array();
		}
		$this->params = $params;
	}

	public function clearParams() {
		$this->setParams(array());
	}

	public function setParam($name, $value = null) {
		if (!self::isStringEmpty($name)) {
			if (!is_array($this->params)) {
				$this->params = array();
			}
			$this->params[$name] = $value;
		}
	}

	public function getParam($name) {
		return $this->params[$name];
	}

	protected function getParamNamesToEscape() {
		return self::$paramNamesToEscape;
	}

	protected function getPreparedParams() {
		return $this->prepareParams($this->getParams());
	}

	protected function getApiBaseUrl() {
		return $this->getConfigValue('apiBaseUrl');
	}

	public function getUrl() {
		return $this->getApiBaseUrl() . "/" . $this->getSpace() . "/"
			. $this->getName() . "." . $this->getFormat();
	}

	/**
	 * @throws GG_API_Exception if one of method params/settings is invalid.
	 */
	public function validate() {
		// TODO: api: add validator for: format, httpMethod, etc? (mb)
	}

	/**
	 * Call GnataGalten API method.
	 * <br/>
	 * NOTE: GG_API_Exception can be thrown during method call.
	 *
	 * @return GG_API_Response Object that represents method response.
	 */
	public function call() {
		$this->validate();

		$httpRequest = new HTTP_Request2();
		$httpRequest->setHeader("Accept-Encoding", ".*");
		$httpRequest->setUrl($this->getUrl());
		$httpRequest->setMethod($this->getHttpMethod());
		$this->addParamsToRequest($httpRequest);

		$httpResponse = $httpRequest->send();
		return $this->prepareResponse($httpResponse);
	}

	protected function prepareResponse($httpResponse) {
		return new GG_API_Response($httpResponse);
	}

	/**
	 * Add parameters to HTTP request.
	 *
	 * @param HTTP_Request2 $httpRequest
	 * @param array $params
	 */
	protected function addParamsToRequest($httpRequest) {
		$params = $this->getPreparedParams();
		switch ($this->getHttpMethod()) {
			case "POST":
				foreach ($params as $name => $value) {
					$httpRequest->addPostParameter($name, $value);
				}
				break;
			case "GET":
				$url = $httpRequest->getUrl();
				foreach ($params as $name => $value) {
					$url->setQueryVariable($name, $value);
				}
				$httpRequest->setUrl($url);
				break;
			default:
				break;
		}
	}

	public function getRequestParams() {
		$params = array();
		$httpMethod = $_SERVER["REQUEST_METHOD"];
		if ($httpMethod === "GET") {
			$params = $_GET;
		} else if ($httpMethod === "POST") {
			$params = $_POST;
		}
		return $params;
	}

	/**
	 * 1) Converts every array param like:
	 * params = array("array" => ("0" => "a", "1" => "b", "2" => "c"))
	 * to
	 * params = array("array[0]" => "a", "array[1]" => "b", "array[2]" => "c")
	 * <br/>
	 * 2) Add callback param if missed for JSON response format.
	 * <br/>
	 * 3) Escape param names by adding "gg_" prefix to names that need it.
	 * E.g. from OAuth params like "callback" to "gg_callback".
	 *
	 * @return array Prepared params.
	 */
	protected function prepareParams($params) {

		if (!is_array($params) || count($params) <= 0) {
			return array();
		}

		// Fix array params.
		foreach ($params as $key => $value) {
			if (is_array($value)) {
				foreach ($value as $k => $v) {
					$newKey = $key . "[" . $k . "]";
					$params[$newKey] = (string) $v;
				}
				unset($params[$key]);
			}
		}

		$callbackParamName = "callback";

		// Add callback if missed for JSON response format.
		if ($this->isFormatJson()) {
			$thisCallback = $this->getJsonpCallback();
			if (!self::isStringEmpty($thisCallback)) {
				$callback = $params[$callbackParamName];
				if (self::isStringEmpty($callback)) {
					$params[$callbackParamName] = $thisCallback;
				}
			}
		}

		// Escape param names (like "callback") from OAuth.
		foreach ($this->getParamNamesToEscape() as $paramNameToEscape) {
			if (array_key_exists($paramNameToEscape, $params)) {
				$escapedParamName = self::$paramNameEscapePreffix . $paramNameToEscape;
				$params[$escapedParamName] = $params[$paramNameToEscape];
				unset($params[$paramNameToEscape]);
			}
		}

		return $params;
	}

	/**
	 * @param string $str
	 * @return bool "true" if $str is null, not of string type or contains only
	 * whitespaces like spaces, tabs, etc.
	 */
	public static function isStringEmpty($str) {
		return
			null === $str ||
			!is_string($str) ||
			strlen(trim($str)) <= 0;
	}
}

?>
